(() => {
	function JhVirtualScroll(el, { fetch, render, skeleton, afterRender }, extraCount = 10, scrollEl = el) {
		this.el = el; // 目标元素
		this.scrollEl = scrollEl; // 滚动元素[默认是目标元素]
		this.fetch = fetch; // 获取内容方法
		this.render = render; // 渲染方法
		this.skeleton = skeleton; // 创建墓碑方法
		this.afterRender = afterRender; // 渲染完成时触发
		this.firstAttachedIndex = 0; // 开始添加索引
		this.lastAttachedIndex = 0; // 最后添加索引
		this.anchorPoint = { index: 0, offset: 0, scrollTop: 0 }; // 锚点
		this.tombstoneWidth = 0; // 墓碑宽度
		this.tombstoneHeight = 0; // 墓碑高度
		this.vnodeList = []; // 节点列表
		this.loadedIndex = 0; // 已加载索引
		this.isbusy = false; // 是在在忙
		this.extraCount = extraCount; // 额外添加的个数
		this.scrollEl.style.overflowY = 'auto';
		this.scrollEl.style.contain = 'layout';
		this.scrollEl.style.boxSizing = 'border-box';
		this.scrollEl.style.willChange = 'transform';
		this.scrollEl.addEventListener('scroll', e => {
			this.onScroll(e); // 监听滚动事件
		});
		window.addEventListener('resize', () => {
			this.onResize(); // 监听尺寸变化事件
		});
		this.onResize(); // 初始化
	}

	JhVirtualScroll.prototype = {
		/**
		 * 重载
		 * @param {*} func 回调函数
		 */
		onReload(func) {
			this.firstAttachedIndex = 0;
			this.lastAttachedIndex = 0;
			Object.keys(this.anchorPoint).forEach(key => {
				this.anchorPoint[key] = 0;
			});
			this.vnodeList.splice(0, this.vnodeList.length);
			this.loadedIndex = 0;
			this.isbusy = false;
			while (this.el.childNodes.length > 0) {
				this.el.removeChild(this.el.childNodes[0]);
			}
			if (func instanceof Function) func();
			this.onResize();
		},

		/**
		 * 尺寸变化时触发
		 */
		onResize() {
			this.initTombstoneSize();
			this.initVnodeSize();
			this.onScroll();
		},

		/**
		 * 滚动时触发
		 * @param {*} event
		 */
		onScroll(event) {
			if (!this.checkScroll()) return false;
			const diff = this.getScrollDiff();
			if (diff === 0) {
				this.anchorPoint.index = 0;
				this.anchorPoint.offset = 0;
			} else {
				this.anchorPoint = Object.assign(this.anchorPoint, this.calculateOffset(this.anchorPoint, diff));
			}
			const lastAnchorPoint = this.calculateOffset(this.anchorPoint, this.scrollEl.offsetHeight);
			this.firstAttachedIndex = Math.max(this.anchorPoint.index - this.extraCount, 0);
			this.lastAttachedIndex = lastAnchorPoint.index + this.extraCount;
			this.setContent();
		},

		/**
		 * 检查是否可以继续滚动
		 * @return true时允许滚动，false时禁止滚动
		 */
		checkScroll() {
			let allow = false;
			if (!this.isbusy) {
				allow = true;
			} else if (this.scrollEl.scrollTop - this.anchorPoint.scrollTop <= 0) {
				allow = true;
			} else {
				allow = this.lastAttachedIndex < this.loadedIndex;
				if (!allow) {
					this.removeTombstone();
				}
			}
			return allow;
		},

		/**
		 * 初始化墓碑尺寸
		 */
		initTombstoneSize() {
			const tombstone = this.skeleton();
			tombstone.style.position = 'absolute';
			this.el.appendChild(tombstone);
			this.tombstoneWidth = tombstone.offsetWidth || 1;
			this.tombstoneHeight = tombstone.offsetHeight || 1;
			this.el.removeChild(tombstone);
		},

		/**
		 * 初始化元素尺寸
		 */
		initVnodeSize() {
			this.vnodeList.forEach(item => {
				item.width = 0;
				item.height = 0;
			});
		},

		/**
		 * 获取滚动高度差异
		 */
		getScrollDiff() {
			const diff = this.scrollEl.scrollTop - this.anchorPoint.scrollTop;
			this.anchorPoint.scrollTop = this.scrollEl.scrollTop;
			return diff;
		},

		/**
		 * 计算偏移量
		 * @param {*} param0
		 * @param {*} diff
		 */
		calculateOffset({ index, offset }, diff) {
			if (diff === 0) {
				return arguments[0];
			}
			diff += offset;
			let count = 0;
			if (diff > 0) {
				while (
					diff > 0 &&
					index < this.vnodeList.length &&
					this.vnodeList[index].height &&
					this.vnodeList[index].height < diff
				) {
					diff -= this.vnodeList[index].height;
					index++;
				}
				if (index >= this.vnodeList.length || !this.vnodeList[index].height) {
					count = Math.floor(Math.max(diff, 0) / this.tombstoneHeight); // 向下取整
				}
			} else {
				while (diff < 0 && index > 0 && this.vnodeList[index - 1].height) {
					diff += this.vnodeList[index - 1].height;
					index--;
				}
				count = Math.max(-index, Math.ceil(Math.min(diff, 0) / this.tombstoneHeight));
			}
			index += count;
			diff -= this.tombstoneHeight * count;
			return {
				index,
				offset: diff,
			};
		},

		/**
		 * 设置内容
		 */
		setContent() {
			// 初始化节点
			this.vnodeList.forEach((vnode, index) => {
				if (index < this.firstAttachedIndex || index > this.lastAttachedIndex - 1) {
					if (vnode.node) {
						this.el.removeChild(vnode.node);
					}
					vnode.node = null;
				}
			});

			const indexArray = [...Array(this.lastAttachedIndex - this.firstAttachedIndex).keys()].map(
				i => i + this.firstAttachedIndex
			);

			// 填充默认数据
			while (this.vnodeList.length <= indexArray[indexArray.length - 1]) {
				this.vnodeList.push(this.defaultVnode());
			}

			// 插入内容
			indexArray.forEach(index => {
				let node;
				let passCircle = false;
				const vnode = this.vnodeList[index];
				if (vnode.node) {
					if (vnode.node.getAttribute('data-tombstone') && vnode.data) {
						this.el.removeChild(vnode.node); // 检测到元素是墓碑时删除它，后面替换成正式内容
						vnode.node = null;
					} else {
						passCircle = true;
					}
				}
				if (!passCircle) {
					if (vnode.data) {
						node = this.render(vnode.data, index);
					} else {
						node = this.skeleton();
						node.setAttribute('data-tombstone', 'true');
					}
					node.style.position = 'absolute';
					this.el.appendChild(node); // 插入内容
					vnode.offset = 0;
					vnode.node = node;
				}
			});

			// 获取节点真实宽高度
			indexArray.forEach(index => {
				const vnode = this.vnodeList[index];
				if (vnode.data && !vnode.height) {
					vnode.width = vnode.node.offsetWidth;
					vnode.height = vnode.node.offsetHeight;
				}
			});

			this.anchorPoint.scrollTop = 0;
			for (let index = 0; index < this.anchorPoint.index; index++) {
				this.anchorPoint.scrollTop += this.vnodeList[index].height || this.tombstoneHeight;
			}
			this.anchorPoint.scrollTop += this.anchorPoint.offset;

			let index = this.anchorPoint.index;
			let curPos = this.anchorPoint.scrollTop - this.anchorPoint.offset;
			while (index > this.firstAttachedIndex) {
				curPos -= this.vnodeList[index - 1].height || this.tombstoneHeight;
				index--;
			}
			while (index < this.firstAttachedIndex) {
				curPos += this.vnodeList[index].height || this.tombstoneHeight;
				index++;
			}

			indexArray.forEach(index => {
				const vnode = this.vnodeList[index];
				if (curPos !== vnode.offset) {
					vnode.node.style.transform = 'translateY(' + curPos + 'px)';
				}
				vnode.offset = curPos;
				curPos += vnode.height || this.tombstoneHeight;
			});

			// 手动跳转到当前位置
			this.scrollEl.scrollTop = this.anchorPoint.scrollTop;

			// 设置目标元素总高度
			this.autoSetHeight();

			this.fetchData();
		},

		/**
		 * 默认元素内容
		 */
		defaultVnode() {
			return {
				data: null,
				node: null,
				height: 0,
				width: 0,
				offset: 0,
			};
		},

		/**
		 * 异步获取信息
		 */
		fetchData() {
			if (this.isbusy || this.lastAttachedIndex <= this.loadedIndex) return false;
			this.isbusy = true;
			const res = this.fetch();
			(res instanceof Promise ? res : Promise.resolve(res))
				.then(datas => {
					if (datas instanceof Array) {
						datas.forEach(data => {
							if (this.vnodeList.length - 1 < this.loadedIndex) {
								this.vnodeList.push(this.defaultVnode());
							}
							this.vnodeList[this.loadedIndex++].data = data;
						});
						if (datas.length > 0) {
							this.isbusy = false;
							this.setContent();
						} else {
							this.isbusy = true;
							this.removeTombstone();
						}
					}
				})
				.catch(() => {
					this.removeTombstone();
				});
		},

		/**
		 * 没有数据时删除多余墓碑元素
		 */
		removeTombstone() {
			let sum = 0;
			for (let index = this.vnodeList.length - 1; index >= 0; index--) {
				const vnode = this.vnodeList[index];
				if (!vnode.data && vnode.node && vnode.node.getAttribute('data-tombstone')) {
					this.el.removeChild(vnode.node);
					vnode.node = null;
				} else if (++sum > this.extraCount / 2) {
					// 确保不会存在多余元素
					break;
				}
			}
		},

		/**
		 * 自动设置目标元素总高度
		 */
		autoSetHeight() {
			for (let i = this.vnodeList.length - 1; i > 0; i--) {
				const vnode = this.vnodeList[i];
				if (vnode.node && vnode.data) {
					const height = vnode.offset + vnode.height;
					this.el.setAttribute('data-height', height);
					if (this.el !== this.scrollEl) {
						this.el.style.height = height + 'px';
					}
					if (this.afterRender instanceof Function) {
						this.afterRender(vnode);
					}
					break;
				}
			}
		},
	};

	// 暴露接口
	window.JhVirtualScroll = JhVirtualScroll;
})();
